﻿using AiMoBridge.Interface;
using AiMoBridge.Paths;
using AiMoWindows.CodeScripts;
using AiMoWindows.CodeScripts.IO;
using AiMoWindows.Compiler;
using AiMoWindows.Entrance;
using AiMoWindows.Localization;
using AiMoWindows.MainActions.Mode;
using AiMoWindows.MainActions.SecondaryActions;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace AiMoWindows.MainActions.LibraryActions
{
    class ScriptsLibraryManager
    {
        RhinoScriptsCompiler Compiler { get; } = new RhinoScriptsCompiler();
        DirectoryInfo Folder { get; }

        readonly IDebugPrinter Printer;
        readonly IExecutor? Executor;
        public ScriptsLibraryManager(IExecutor? executor, string folder, IDebugPrinter printer)
        {
            Executor = executor;
            Printer = printer;
            Folder = new DirectoryInfo(folder);
            if (!Folder.Exists)
            {
                Folder.Create();
            }
        }
        public List<ScriptsLibraryItem> LoadExistedItems()
        {
            List<ScriptsLibraryItem> items = new List<ScriptsLibraryItem>();
            foreach (FileInfo file in Folder.GetFiles($"*{CodeIO.Extension}", SearchOption.TopDirectoryOnly))
            {
                if (CheckCommandExisted(file, items.Select(x => x.Name)))
                {
                    ScriptsLibraryItem? item = CreateCommandItem(file);
                    if (item != null) items.Add(item);
                }
            }
            items.Sort(CompareItem);
            return items;
        }

        public FileInfo? CreateDownloadScriptFile(ScriptDownloadResult result)
        {
            ScriptComplileMode mode = (AiMoProgram.Instance.AiMoExecuterMode == ExecuterMode.Grasshopper) ?
                ScriptComplileMode.GrasshopperScript :
                ScriptComplileMode.RhinoScript;
            ScriptSource scriptSource = new ScriptSource(ScriptComplileMode.RhinoScript, null, true);
            bool re = result.SyncToScriptSource(scriptSource);
            if (!string.IsNullOrEmpty(result.SyncToScriptInfo))
                Printer.PrintDebug(result.SyncToScriptInfo);
            if (re)
            {
                string path = "";
                try
                {
                    string name = scriptSource.Attributes.CodeName;
                    if (string.IsNullOrWhiteSpace(name)) name = scriptSource.Attributes.Description.Trim();
                    if (name.Length > 20) name = name[..20];
                    string fileName = GetValidFileName(name);
                    path = Path.Combine(Folder.FullName, fileName + CodeIO.Extension);
                    byte[] saveBytes = CodeIO.GetBytes(scriptSource, false);
                    File.WriteAllBytes(path, saveBytes);
                }
                catch (Exception ex)
                {
                    Printer.PrintDebug($"Add Download Error: {ex.Message}");
                }
                return new FileInfo(path);
            }
            return null;
        }
        string GetValidFileName(string fileName)
        {
            bool replaceName = false;
            string newName = "";
            char[] invalidChars = Path.GetInvalidFileNameChars();
            foreach (char c in fileName)
            {
                if (invalidChars.Contains(c))
                {
                    replaceName = true;
                    newName += "_";
                }
                else newName += c;
            }
            if (replaceName) fileName = newName;
            if (string.IsNullOrWhiteSpace(fileName))
            {
                fileName = "Downloaded_Scipts_" + Guid.NewGuid().ToString()[..6];
            }
            return fileName;
        }

        public string CompiledInfo = "";
        public async Task<string> ExportRhp(ScriptsLibraryItem[] canExportItems, string authorText)
        {
            CompiledInfo = "";
            try
            {
                string tempDllPath = "";
                await Task.Run(() =>
                {
                    Stopwatch stopwatch = Stopwatch.StartNew();
                    RhpCompileSource rhpCompileSource = new RhpCompileSource(canExportItems, authorText);
                    StringBuilder compileErrors = new StringBuilder();
                    CompiledResult resultPath = Compiler.CompileScriptInstance(rhpCompileSource, compileErrors);
                    tempDllPath = resultPath.DllPath;
                    stopwatch.Stop();
                    CompiledInfo += string.Format(Local.Get("Compiled {0} script items in {1} s"), canExportItems.Length, stopwatch.ElapsedMilliseconds * 0.001);
                    if (string.IsNullOrEmpty(tempDllPath)) CompiledInfo += compileErrors.ToString();
                });
                return tempDllPath;
            }
            catch (Exception ex)
            {
                CompiledInfo += string.Format(Local.Get("Compile rhp error {0}"), ex);
                return "";
            }
        }


        public bool UpdateCompileItem(ScriptsLibraryItem item)
        {
            if (!item.ReloadScript(out string error))
            {
                Printer.PrintDebug($"{Local.Get("Compile Command from")}: \"{item.Name}\" {Local.Get("Error:")}" + error);
                return false;
            }
            StringBuilder compileErrors = new StringBuilder();
            if (!item.Compile(Compiler, compileErrors))
            {
                Printer.PrintDebug($"{Local.Get("Compile Command from")}: \"{item.Name}\" {Local.Get("Error:")}{Environment.NewLine}{compileErrors}");
                return false;
            }
            return true;
        }
        public bool RunItem(ScriptsLibraryItem item)
        {
            if (!item.GetRunningDll(out string runningDll, out string error))
            {
                Printer.PrintDebug(error);
                return false;
            }

            ExecuterMode executerMode = AiMoProgram.Instance.AiMoExecuterMode;
            if (!Utils.CheckScriptCanSendToExecutor(runningDll, Executor, executerMode, item.Script.ScriptsMode, Printer))
            {
                return false;
            }
            // CheckScriptCanSendToExecutor 已经检查过 Executor!
            bool re = Executor!.RunSriptCommand(new ExecutorInfo(runningDll, Printer, Array.Empty<string>()));
            Printer.PrintDebug(Executor.RunCommandError);
            Printer.PrintDebug(Executor.RunCommandTime);

            if (re && executerMode == ExecuterMode.Grasshopper)
            {
                Printer.PrintDebug("Script is sent to GH and saved in grasshopper component");
            }
            return re;
        }

        public bool CheckCommandExisted(FileInfo file, IEnumerable<string> existedNames)
        {
            foreach (string name in existedNames)
            {
                if (name == Path.GetFileNameWithoutExtension(file.Name))
                {
                    Printer.PrintDebug(file.Name + Local.Get(" is existed"));
                    return false;
                }
            }
            return true;
        }

        public ScriptsLibraryItem? CreateCommandItem(FileInfo file)
        {
            try
            {
                if (file.Extension != CodeIO.Extension)
                {
                    Printer.PrintDebug(Local.Get("Only .AiMoScript file can be loaded"));
                    return null;
                }
                FileInfo duplicateFile;
                if (file.Directory?.FullName == PathManager.CustomizedCommandsFolder)
                {
                    duplicateFile = file;
                }
                else
                {
                    duplicateFile = file.CopyTo(Path.Combine(PathManager.CustomizedCommandsFolder, file.Name), true);
                }
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                ScriptsLibraryItem? item = ScriptsLibraryItem.BuildCommandItem(duplicateFile, out string error);
                stopwatch.Stop();
                if (item != null)
                {

                    StringBuilder compileErrors = new StringBuilder();
                    if (!item.Compile(Compiler, compileErrors))
                    {
                        Printer.PrintDebug($"{Local.Get("Compile Command from")}: \"{file.Name}\" {Local.Get("Error:")}{Environment.NewLine}{compileErrors}");
                    }
                    else
                    {
                        Printer.PrintDebug($"{Local.Get("Loaded Command")}: \"{file.Name}\" ({stopwatch.ElapsedMilliseconds * 0.001}s)");
                        return item;
                    }
                }
                else
                {
                    Printer.PrintDebug($"{Local.Get("Load Command from")} {file.FullName} {Local.Get("Error:")}{Environment.NewLine}{error}");
                }

            }
            catch (Exception ex)
            {
                Printer.PrintDebug($"Cannot Add file {file.Name}\r\n{ex}");

            }
            return null;
        }

        int CompareItem(ScriptsLibraryItem a, ScriptsLibraryItem b)
        {
            return a.Name.CompareTo(b.Name);
        }
    }
}
